#region Header

// MS codename Velocity Distributed Cache Extensions.
// (c) 2009

#endregion


#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Caching.Extensions.Core.Impl;

#endregion


namespace Caching.Extensions.Core
{
    /// <summary>
    /// public <see cref="SmartCache"/> events.
    /// </summary>
    public class SmartCacheEventPublisher : IDisposable
    {
        #region Private data 

        private readonly List<ISmartCacheEventSink> m_subscribers = new List<ISmartCacheEventSink>();
        private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        private readonly object m_owner;
        private bool m_disposed;


        /// <summary>
        /// Initializes a new instance of the <see cref="SmartCacheEventPublisher"/> class.
        /// </summary>
        /// <param name="owner">The owner.</param>
        public SmartCacheEventPublisher(object owner)
        {
            if (owner == null) throw new ArgumentNullException("owner");
            if (!((owner is ISmartCacheLocalStorage) || (owner is ISmartCache)))
                throw new ArgumentException("Owner must be ISmartCache or ISmartCacheLocalStorage", "owner");
            m_owner = owner;
        }

        #endregion Private data


        #region Subscription management

        /// <summary>
        /// Gets the number of subscribers.
        /// </summary>
        /// <value>The subscriber count.</value>
        public int SubscriberCount
        {
            get
            {
                CheckDisposed();
                m_lock.EnterReadLock();
                try
                {
                    return m_subscribers.Count;
                }
                finally
                {
                    m_lock.ExitReadLock();
                }
            }
        }


        /// <summary>
        /// Register subscriber.
        /// </summary>
        /// <param name="subscriber">The subscriber.</param>
        /// <returns>true of subscriber was sucessfuly registered.</returns>
        public bool Subscribe(ISmartCacheEventSink subscriber)
        {
            if (subscriber == null) throw new ArgumentNullException("subscriber");
            CheckDisposed();

            m_lock.EnterWriteLock();
            try
            {
                if (m_subscribers.Contains(subscriber))
                    return false;
                m_subscribers.Add(subscriber);
                return true;
            }
            finally
            {
                m_lock.ExitWriteLock();
            }
        }


        /// <summary>
        /// unregister subscriber.
        /// </summary>
        /// <param name="subscriber">The subscriber.</param>
        /// <returns>true if subscriber was sucesfuly unregistered.</returns>
        public bool Unsubscribe(ISmartCacheEventSink subscriber)
        {
            if (subscriber == null) throw new ArgumentNullException("subscriber");
            CheckDisposed();

            m_lock.EnterWriteLock();
            try
            {
                return m_subscribers.Remove(subscriber);
            }
            finally
            {
                m_lock.ExitWriteLock();
            }
        }

        #endregion Subscription management


        #region Event publishing

        /// <summary>
        /// Publish remote cache item operation.
        /// </summary>
        /// <param name="itemOperation">The item operation.</param>
        /// <param name="key">The key.</param>
        public void RemoteCacheItemOperation(SmartCacheItemOperation itemOperation, SmartCacheKey key)
        {
            CheckDisposed();
            var notification = new SmartCacheRemoteItemNotification(key, itemOperation);
            PublishNotification(sink => sink.RemoteCacheItemOperation(m_owner, notification));
        }


        /// <summary>
        /// Publish remote cache error.
        /// </summary>
        /// <param name="cacheName">Name of the cache.</param>
        /// <param name="ex">The esception.</param>
        /// <param name="msg">The message.</param>
        public void RemoteCacheError(string cacheName, Exception ex, string msg)
        {
            CheckDisposed();
            var notification = new SmartCacheExceptionNotification(cacheName, ex, msg);
            PublishNotification(sink => sink.RemoteCacheError(m_owner, notification));
        }


        /// <summary>
        /// Publish region operation.
        /// </summary>
        /// <param name="cacheName">Name of the cache.</param>
        /// <param name="region">The region name.</param>
        /// <param name="operation">The operation.</param>
        public void RegionOperation(string cacheName, string region, SmartCacheRegionOperation operation)
        {
            CheckDisposed();
            var notification = new SmartCacheRegionNotification(cacheName, region, operation);
            PublishNotification(sink => sink.RegionOperation(m_owner, notification));
        }


        /// <summary>
        /// Publish local cache operation.
        /// </summary>
        /// <param name="operation">The operation.</param>
        /// <param name="key">The key.</param>
        public void LocalCacheItemOperation(LocalCacheItemOperation operation, SmartCacheKey key)
        {
            CheckDisposed();
            var notification = new SmartCacheLocalItemNotification(key, operation);
            PublishNotification(sink => sink.LocalCacheItemNotification(m_owner, notification));
        }


        /// <summary>
        /// Publish local cache cleanup operation.
        /// </summary>
        /// <param name="cleanedCount">The number of cleaned items.</param>
        /// <param name="cleanupTime">The cleanup time.</param>
        public void LocalCacheCleanup(int cleanedCount, TimeSpan cleanupTime)
        {
            CheckDisposed();
            var notification = new SmartCacheCleanupNotification(cleanedCount, cleanupTime);
            PublishNotification(sink => sink.LocalCacheCleanup(m_owner, notification));
        }


        /// <summary>
        /// Publish local cache error.
        /// </summary>
        /// <param name="ex">The exception occured.</param>
        /// <param name="msg">The message.</param>
        public void LocalCacheError(Exception ex, string msg)
        {
            CheckDisposed();
            var notification = new SmartCacheExceptionNotification("<LocalCache>", ex, msg);
            PublishNotification(sink => sink.LocalCacheError(m_owner, notification));
        }

        #endregion Event publishing


        #region EventPublication core

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", 
            Justification = "Need to swallow exeption to deliver message to other sinks")]
        private void PublishNotification(Action<ISmartCacheEventSink> action)
        {
            m_lock.EnterReadLock();
            try
            {
                foreach (ISmartCacheEventSink sink in m_subscribers)
                {
                    try
                    {
                        action(sink);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(
                            string.Format(CultureInfo.InvariantCulture, "PublishEvent: Exception catched [{0}]",
                                ex.Message),
                            "SmartCacheEventPublisher");
                    }
                }
            }
            finally
            {
                m_lock.ExitReadLock();
            }
        }

        #endregion EventPublication core


        #region IDsiposable implementation 

        protected void CheckDisposed()
        {
            if (m_disposed) throw new ObjectDisposedException(GetType().FullName);
        }


        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        private void Dispose(bool disposing)
        {
            if (m_disposed)
                return;

            if (disposing)
            {
                m_lock.EnterWriteLock();
                m_subscribers.Clear();
                m_lock.ExitWriteLock();
            }

            m_lock.Dispose();
            m_disposed = true;
        }


        ~SmartCacheEventPublisher()
        {
            Dispose(false);
        }

        #endregion IDsiposable implementation
    }
}